#include "QtOSGWidget.h"
#include <cassert>
#include <QtCore/QDebug>

#define WITH_SELECTION_PROCESSING 
using YY::QtOSGWidget;

namespace
{

#ifdef WITH_SELECTION_PROCESSING
	QRect makeRectangle(const QPoint& first, const QPoint& second)
	{
		// Relative to the first point, the second point may be in either one of the
		// four quadrants of an Euclidean coordinate system.
		//
		// We enumerate them in counter-clockwise order, starting from the lower-right
		// quadrant that corresponds to the default case:
		//
		//            |
		//       (3)  |  (4)
		//            |
		//     -------|-------
		//            |
		//       (2)  |  (1)
		//            |

		if (second.x() >= first.x() && second.y() >= first.y())
			return QRect(first, second);
		else if (second.x() < first.x() && second.y() >= first.y())
			return QRect(QPoint(second.x(), first.y()), QPoint(first.x(), second.y()));
		else if (second.x() < first.x() && second.y() < first.y())
			return QRect(second, first);
		else if (second.x() >= first.x() && second.y() < first.y())
			return QRect(QPoint(first.x(), second.y()), QPoint(second.x(), first.y()));

		// Should never reach that point...
		return QRect();
	}
#endif

}

QtOSGWidget::QtOSGWidget(qreal scaleX, qreal scaleY, QWidget* parent)
	: QOpenGLWidget(parent)
	, _mGraphicsWindow(new osgViewer::GraphicsWindowEmbedded(this->x(), this->y(),
		this->width(), this->height()))
	, _mViewer(new osgViewer::Viewer)
	, m_scaleX(scaleX)
	, m_scaleY(scaleY)
	, selectionActive_(false)
{
	osg::Camera* camera = new osg::Camera;
	camera->setViewport(0, 0, this->width(), this->height());
	camera->setClearColor(osg::Vec4(0.9f, 0.9f, 1.f, 1.f));
	float aspectRatio = static_cast<float>(this->width()) / static_cast<float>(this->height());
	camera->setProjectionMatrixAsPerspective(30.f, aspectRatio, 1.f, 1000.f);
	camera->setGraphicsContext(_mGraphicsWindow);

	_mViewer->setCamera(camera);
	/*_mViewer->setSceneData(osgDB::readNodeFile("F:/Other/model/20160908.ive"));*/
	_mViewer->addEventHandler(new osgViewer::StatsHandler);

	osgGA::TrackballManipulator* manipulator = new osgGA::TrackballManipulator;
	manipulator->setAllowThrow(false);
	_mViewer->setCameraManipulator(manipulator);

	_mViewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
	_mViewer->realize();

	// This ensures that the widget will receive keyboard events. This focus
	// policy is not set by default. The default, Qt::NoFocus, will result in
	// keyboard events that are ignored.
	this->setFocusPolicy(Qt::StrongFocus);
	this->setMinimumSize(100, 100);

	// Ensures that the widget receives mouse move events even though no
	// mouse button has been pressed. We require this in order to let the
	// graphics window switch viewports properly.
	this->setMouseTracking(true);
}

QtOSGWidget::~QtOSGWidget() 
{

}

void QtOSGWidget::setScale(qreal X, qreal Y)
{
	m_scaleX = X;
	m_scaleY = Y;
	this->resizeGL(this->width(), this->height());
}

void QtOSGWidget::keyPressEvent(QKeyEvent* event)
{
	QString keyString = event->text();
	const char* keyData = keyString.toLocal8Bit().data();

	if (event->key() == Qt::Key_S)
	{
#ifdef WITH_SELECTION_PROCESSING
		selectionActive_ = !selectionActive_;
#endif

		// Further processing is required for the statistics handler here, so we do
		// not return right away.
	}
	else if (event->key() == Qt::Key_H)
	{
		this->onHome();
		return;
	}

	this->getEventQueue()->keyPress(osgGA::GUIEventAdapter::KeySymbol(*keyData));
}

void QtOSGWidget::keyReleaseEvent(QKeyEvent* event)
{
	QString keyString = event->text();
	const char* keyData = keyString.toLocal8Bit().data();

	this->getEventQueue()->keyRelease(osgGA::GUIEventAdapter::KeySymbol(*keyData));
}

void QtOSGWidget::onHome()
{
	_mViewer->home();
}

void QtOSGWidget::setKeyboardModifiers(QInputEvent* event)
{
	int modkey = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier);
	unsigned int mask = 0;
	if (modkey & Qt::ShiftModifier) mask |= osgGA::GUIEventAdapter::MODKEY_SHIFT;
	if (modkey & Qt::ControlModifier) mask |= osgGA::GUIEventAdapter::MODKEY_CTRL;
	if (modkey & Qt::AltModifier) mask |= osgGA::GUIEventAdapter::MODKEY_ALT;
	this->getEventQueue()->getCurrentEventState()->setModKeyMask(mask);
}

void QtOSGWidget::paintGL() {
	_mViewer->frame();
}

void QtOSGWidget::resizeGL(int width, int height)
{
	this->getEventQueue()->windowResize(this->x()*m_scaleX, this->y() * m_scaleY, width*m_scaleX, height*m_scaleY);
	_mGraphicsWindow->resized(this->x()*m_scaleX, this->y() * m_scaleY, width*m_scaleX, height*m_scaleY);
	osg::Camera* camera = _mViewer->getCamera();
	camera->setViewport(0, 0, this->width()*m_scaleX, this->height()* m_scaleY);
}

void QtOSGWidget::initializeGL() {
	osg::Node* geode = _mViewer->getSceneData();
	osg::StateSet* stateSet = geode->getOrCreateStateSet();
	osg::Material* material = new osg::Material;
	material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
	stateSet->setAttributeAndModes(material, osg::StateAttribute::ON);
	stateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::ON);
}

void QtOSGWidget::mouseMoveEvent(QMouseEvent* event)
{
	setKeyboardModifiers(event);
	if (selectionActive_ && event->buttons() & Qt::LeftButton)
	{
		selectionEnd_ = event->pos();

		// Ensures that new paint events are created while the user moves the
		// mouse.
		this->update();
	}
	else
	{
		this->getEventQueue()->mouseMotion(event->x()*m_scaleX, event->y()*m_scaleY);
	}
}

void QtOSGWidget::mousePressEvent(QMouseEvent* event)
{
	// Selection processing
	if (selectionActive_ && event->button() == Qt::LeftButton)
	{
		selectionStart_ = event->pos();
		selectionEnd_ = selectionStart_;		// Deletes the old selection
		selectionFinished_ = false;				// As long as this is set, the rectangle will be drawn
	}

	// Normal processing
	else
	{
		unsigned int button = 0;
		switch (event->button())
		{
		case Qt::LeftButton: button = 1; break;
		case Qt::MidButton: button = 2; break;
		case Qt::RightButton: button = 3; break;
		case Qt::NoButton: button = 0; break;
		default: button = 0; break;
		}
		setKeyboardModifiers(event);
		this->getEventQueue()->mouseButtonPress(event->x()*m_scaleX, event->y()*m_scaleY, button);
	}
}

void QtOSGWidget::mouseReleaseEvent(QMouseEvent* event)
{
	// Selection processing: Store end position and obtain selected objects
	// through polytope intersection.
	if (selectionActive_ && event->button() == Qt::LeftButton)
	{
		selectionEnd_ = event->pos();
		selectionFinished_ = true; // Will force the painter to stop drawing the
								   // selection rectangle

		this->processSelection();
	}

	// Normal processing
	else
	{
		unsigned int button = 0;
		switch (event->button())
		{
		case Qt::LeftButton: button = 1; break;
		case Qt::MidButton: button = 2; break;
		case Qt::RightButton: button = 3; break;
		case Qt::NoButton: button = 0; break;
		default: button = 0; break;
		}
		setKeyboardModifiers(event);
		this->getEventQueue()->mouseButtonRelease(event->x()*m_scaleX, event->y()*m_scaleY, button);
	}
}

void QtOSGWidget::wheelEvent(QWheelEvent* event)
{
	// Ignore wheel events as long as the selection is active.
	if (selectionActive_)
		return;

	event->accept();
	int delta = event->delta();

	setKeyboardModifiers(event);
	osgGA::GUIEventAdapter::ScrollingMotion motion = delta > 0 ?
		osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN;

	this->getEventQueue()->mouseScroll(motion);
}

bool QtOSGWidget::event(QEvent* event)
{
	bool handled = QOpenGLWidget::event(event);
	this->update();
	return handled;
}

osgGA::EventQueue* QtOSGWidget::getEventQueue() const {
	osgGA::EventQueue* eventQueue = _mGraphicsWindow->getEventQueue();

	if (eventQueue)
		return eventQueue;
	else
		throw std::runtime_error("Unable to obtain valid event queue");;
}

void QtOSGWidget::onResize(int width, int height)
{
	std::vector<osg::Camera*> cameras;
	_mViewer->getCameras(cameras);

	assert(cameras.size() == 1);

	cameras[0]->setViewport(0, 0, width, height);
}

void QtOSGWidget::processSelection()
{
#ifdef WITH_SELECTION_PROCESSING
	QRect selectionRectangle = makeRectangle(selectionStart_, selectionEnd_);
	int widgetHeight = this->height();

	double xMin = selectionRectangle.left();
	double xMax = selectionRectangle.right();
	double yMin = widgetHeight - selectionRectangle.bottom();
	double yMax = widgetHeight - selectionRectangle.top();

	osgUtil::PolytopeIntersector* polytopeIntersector
		= new osgUtil::PolytopeIntersector(osgUtil::PolytopeIntersector::WINDOW,
			xMin, yMin,
			xMax, yMax);

	// This limits the amount of intersections that are reported by the
	// polytope intersector. Using this setting, a single drawable will
	// appear at most once while calculating intersections. This is the
	// preferred and expected behaviour.
	polytopeIntersector->setIntersectionLimit(osgUtil::Intersector::LIMIT_ONE_PER_DRAWABLE);

	osgUtil::IntersectionVisitor iv(polytopeIntersector);


	osgViewer::View* view = _mViewer.get();

	if (!view)
		throw std::runtime_error("Unable to obtain valid view for selection processing");

	osg::Camera* camera = view->getCamera();

	if (!camera)
		throw std::runtime_error("Unable to obtain valid camera for selection processing");

	camera->accept(iv);

	if (polytopeIntersector->containsIntersections())
	{

		auto intersections = polytopeIntersector->getIntersections();

		for (auto&& intersection : intersections)
			qDebug() << "Selected a drawable:" << QString::fromStdString(intersection.drawable->getName());
	}
	
#endif
}
